#include "process.h"
#include "cpu.h"
#include <malloc.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <pthread.h>

int initializeProcesses(CPU cpu, int num_ui, int num_calc, int num_prod, int num_cons);
void timerInterruptHandler(CPU cpu);
void ioInterruptHandler(CPU cpu);
void keyboardInterruptHandler(CPU cpu);
void systemCallHandler(Process p, CPU cpu);
void setupIOBlockQueues(const int num_devices, Queue block_queue[]);


CPU newCPU(int num_ui, int num_calc, int num_prod, int num_cons, int scheduler_type) {
	Process* processes;
	CPU cpu = (CPU) malloc(sizeof(CPUStr));
	processes = (Process*) malloc(MAX_PROCESSES * sizeof(Process));
	cpu->process = processes;
	cpu->lock = PTHREAD_MUTEX_INITIALIZER;
	cpu->cpu_pause = 0;
	cpu->pc = 0;
	cpu->sched = newScheduler(MAX_PROCESSES, scheduler_type, (CPU_SCHED) cpu);
	cpu->num_processes = initializeProcesses(cpu, num_ui, num_calc, num_prod, num_cons);
	initializeScheduler(cpu->sched);
	cpu->memory = newMemory((CPU_MEM) cpu, MAX_PROCESSES);
	cpu->printer = newPrinter((CPU_PRNTR) cpu);
	cpu->timer = initializeTimer(timerInterruptHandler, (CPU_TIMER) cpu);
	cpu->io[IO] = initializeIODevice(ioInterruptHandler, IO, (CPU_IO) cpu);
	cpu->io[KEYBOARD] = initializeIODevice(keyboardInterruptHandler, KEYBOARD, (CPU_IO) cpu);
	setupIOBlockQueues(NUM_IO, cpu->block_queue);

	return cpu;
}

int initializeProcesses(CPU cpu, int num_ui, int num_calc, int num_prod, int num_cons) {
	Process p;
	int i;
	int id = 0;
	for (i = 0; i < num_ui; i++) {
		p = newProcess(id, 1000, 400, UI);
		addToReadyQueue(cpu->sched, p);
		cpu->process[id] = p;
		id = id + 1;
	}
	for (i = 0; i < num_calc; i++) {
		p = newProcess(id, 1000, 400, CALCULATOR);
		addToReadyQueue(cpu->sched, p);
		cpu->process[id] = p;
		id = id + 1;
	}
	for (i = 0; i < num_prod; i++) {
		p = newProcess(id, 1000, 400, PRODUCER);
		addToReadyQueue(cpu->sched, p);
		cpu->process[id] = p;
		id = id + 1;
	}
	for (i = 0; i < num_cons; i++) {
		p = newProcess(id, 1000, 400, CONSUMER);
		addToReadyQueue(cpu->sched, p);
		cpu->process[id] = p;
		id = id + 1;
	}
	return id;
}

void runCPU(const CPU cpu) {
	int system_call;
	Process p;
	int pause;
	startTimer(cpu->timer);
	p = getRunningProcess(cpu->sched);
	while (1) {
		cpu->pc = p->pcb->next_step;
		while (p->pcb->state == RUNNING) {
			do {
				pause = pthread_mutex_lock(&cpu->lock);
			} while (pause != 0);
			cpu->cpu_pause = CPU_PAUSE;
			if (p->pcb->state != RUNNING) {
				pthread_mutex_unlock(&cpu->lock);
				break;
			}
			system_call = processInstruction(p, cpu->pc);
			cpu->pc = (cpu->pc + 1) % p->num_instructions;
			if (system_call != -1) {
				systemCallHandler(p, cpu);
			}
			cpu->cpu_pause = 0;
			pthread_mutex_unlock(&cpu->lock);
			milliSleep(DEFAULT_PAUSE);
		}
		do {
			pause = pthread_mutex_lock(&cpu->lock);
		} while (pause != 0);
		cpu->cpu_pause = CPU_PAUSE;
		p = switchProcess(cpu->sched, cpu->pc);
		cpu->cpu_pause = 0;
		pthread_mutex_unlock(&cpu->lock);
	}
	return;
}

void memoryInterruptHandler(CPU cpu, Process p) {
	int pause;
	do {
		pause = pthread_mutex_lock(&cpu->lock);
	} while (pause != 0);
	cpu->cpu_pause = CPU_PAUSE;
	if (p->process_type == CONSUMER) {
		print(cpu->printer, newPrinterRequest(CONS_INT, NULL_CALL, p->pcb->process_id, 0));
	} else {
		print(cpu->printer, newPrinterRequest(PROD_INT, NULL_CALL, p->pcb->process_id, 0));
	}
	p->pcb->state = READY;
	addToReadyQueue(cpu->sched, p);
	cpu->cpu_pause = 0;
	pthread_mutex_unlock(&cpu->lock);
	return;
}

void timerInterruptHandler(CPU cpu) {
	Process p;
	int pause;
	do {
		pause = pthread_mutex_lock(&cpu->lock);
	} while (pause != 0);
	cpu->cpu_pause = CPU_PAUSE;
	p = getRunningProcess(cpu->sched);
	if (p->pcb->state != BLOCKED) {
		p->pcb->state = INTERRUPTED;
	}
	print(cpu->printer, newPrinterRequest(TIMER_INT, NULL_CALL, 0, 0));
	cpu->cpu_pause = 0;
	pthread_mutex_unlock(&cpu->lock);
	return;
}

void keyboardInterruptHandler(CPU cpu) {
	Process p;
	int pause;
	do {
		pause = pthread_mutex_lock(&cpu->lock);
	} while (pause != 0);
	cpu->cpu_pause = CPU_PAUSE;
	if (!isEmpty(cpu->block_queue[KEYBOARD])) {
		p = frontAndDequeue(cpu->block_queue[KEYBOARD]);
		p->pcb->state = READY;
		addToReadyQueue(cpu->sched, p);
		print(cpu->printer, newPrinterRequest(KEYBOARD_INT, NULL_CALL, 0, 0));
	}
	if (!isEmpty(cpu->block_queue[KEYBOARD])) {
		startIODevice(cpu->io[KEYBOARD]);
	}
	cpu->cpu_pause = 0;
	pthread_mutex_unlock(&cpu->lock);
	return;
}

void ioInterruptHandler(CPU cpu) {
	Process p;
	int pause;
	do {
		pause = pthread_mutex_lock(&cpu->lock);
	} while (pause != 0);
	cpu->cpu_pause = CPU_PAUSE;
	if (!isEmpty(cpu->block_queue[IO])) {
		p = frontAndDequeue(cpu->block_queue[IO]);
		p->pcb->state = READY;
		addToReadyQueue(cpu->sched, p);
		print(cpu->printer, newPrinterRequest(IO_INT, NULL_CALL, 0, 0));
	}
	if (!isEmpty(cpu->block_queue[IO])) {
		startIODevice(cpu->io[IO]);
	}
	cpu->cpu_pause = 0;
	pthread_mutex_unlock(&cpu->lock);
	return;
}

void systemCallHandler(Process p, CPU cpu) {
	switch (p->process_type) {
	case UI:
		p->pcb->state = BLOCKED;
		if (isEmpty(cpu->block_queue[KEYBOARD])) {
			//if there are no blocked keyboard devices running
			startIODevice(cpu->io[KEYBOARD]);
		}
		enqueue(p, cpu->block_queue[KEYBOARD]);
		print(cpu->printer, newPrinterRequest(NULL_INT, KEYBOARD_CALL, p->pcb->process_id, 0));
		break;
	case CALCULATOR:
		p->pcb->state = BLOCKED;
		if (isEmpty(cpu->block_queue[IO])) {
			//if there are no blocked keyboard devices running
			startIODevice(cpu->io[IO]);
		}
		enqueue(p, cpu->block_queue[IO]);
		print(cpu->printer, newPrinterRequest(NULL_INT, IO_CALL, p->pcb->process_id, 0));
		break;
	case PRODUCER:
		incrementMemory(cpu->memory, p);
		print(cpu->printer, newPrinterRequest(NULL_INT, PRODUCER_CALL, p->pcb->process_id, p->pcb->state));
		break;
	case CONSUMER:
		decrementMemory(cpu->memory, p);
		print(cpu->printer, newPrinterRequest(NULL_INT, CONSUMER_CALL, p->pcb->process_id, p->pcb->state));
		break;
	}
	return;
}

void setupIOBlockQueues(const int num_devices, Queue block_queue[]) {

	int i;
	for (i = 0; i < num_devices; i++) {
		block_queue[i] = createQueue(MAX_PROCESSES);
	}
}
